# -*- coding: UTF-8 -*-

from datetime import datetime
import sys
import time
from sqlalchemy import and_,  func, or_
from typing import Dict, List, Any, Generator

from sqlalchemy import Boolean, Column, DateTime, Integer, String, Index, Table, ForeignKey, text, JSON, TEXT, distinct, FLOAT
from sqlalchemy.orm import Session, relationship
from common.database import Base
from common.database import SessionMixin
from common.utils import is_empty
from core.logger import logger


__all__ = ['CeleryTask']

class CeleryTask(Base, SessionMixin):
    '''
    celery异步任务记录表
    '''
    __tablename__ = "celery_task"
    #
    name = Column(String(128), nullable=False, comment='任务名称')
    type = Column(String(128), nullable=False, comment='任务类型')
    tid = Column(String(128), nullable=False, default=0, comment='任务id')
    relation_uuid = Column(Integer, nullable=True, default=0, comment='关联其他uuid')
    # 四种状态：STARTED:任务已启动 PROGRESS:任务正在处理中 SUCCESS:结果成功 FAILURE:结果失败
    state = Column(String(128), nullable=False, default=0, comment='任务状态')
    progress = Column(Integer, default=0, nullable=False, comment='任务进度')
    start_at = Column(DateTime, nullable=False, comment='任务启动时间')
    ends_at = Column(DateTime, nullable=True, comment='任务结束时间')
    update_at = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment='更新时间')
    comment = Column(String(256), nullable=True, comment='任务说明')

    __table_args__ = (
        Index('idx_bill_task_tid', 'tid', unique=True),
        Index('idx_bill_task_type', 'type', unique=False),
        Index('idx_bill_task_state', 'state', unique=False),
        {
            'mysql_engine': 'innodb',
            'mysql_charset': 'utf8mb4',
            'mysql_collate': 'utf8mb4_unicode_ci',
            'extend_existing': True,
            'mysql_row_format': 'dynamic'
        }
    )

    def __repr__(self):
        return f"<type='{self.type}', {self.tid}'>"

    def object_to_dict(self, db: Session):
        result = {
            key: getattr(self, key)
            for key in self.__mapper__.c.keys()
        }
        return result


    # 根据tid查询任务，判断任务是否存在
    @classmethod
    def get_object_by_tid(cls, db: Session, tid: str):
        object = db.query(cls).filter(
            cls.tid == tid
        ).first()
        return object

    # 根据name查询任务，判断任务是否存在
    @classmethod
    def get_object_by_name(cls, db: Session, name: str):
        object = db.query(cls).filter(
            cls.name == name
        ).first()
        return object



    # 根据tid查询任务，判断任务是否存在
    @classmethod
    def check_object_by_tid(cls, db: Session, tid: str):
        for i in range(15):
            object = cls.get_object_by_tid(db, tid)
            if not object:
                # 总共大概2分钟，120秒
                time.sleep(i+1)
                logger.info(f"check celery task id: {tid} => {i}")
                continue
            return True
        return False



    # 根据tid查询任务的详细信息
    @classmethod
    def get_object_info_by_tid(cls, db: Session, tid: str):
        object = cls.get_object_by_tid(db, tid)
        return object.object_to_dict(db)


    # 判断一种任务是否有任务在跑
    @classmethod
    def is_progress_on_name(cls, db: Session, name: str):
        result = db.query(cls).filter(
            cls.name == name,
            or_(cls.state == "STARTED", cls.state == "PROGRESS")
        ).first()

        return bool(result)


    # 更新任务状态
    @classmethod
    def update_task(cls, db: Session, tid: str, state: str=None, progress: int=None, comment: str=None):
        task_info = cls.get_object_info_by_tid(db, tid)
        if state:
            task_info["state"] = state
            # 记录结束时间
            if state in ["SUCCESS", "FAILURE"]:
                task_info["ends_at"] = datetime.now()
                task_info["progress"] = 100

        if progress: task_info["progress"] = progress
        if comment: task_info["comment"] = comment

        task_info.pop("update_at")
        result = cls.update(db, task_info)
        if not result:
            logger.error(f"update celery task to database failed")
            return False

        return True



